// todo -- 

RipplePlayer{


	var buffer, controls, launchSynthFn, group, parent, poly, sustainedNotes, logger;

	*new{
		^super.new.init;
	}
	
	init{
		poly = true;
		group = Group.new; // every synth launched from this SL lives under this group
		sustainedNotes = List.new;
		logger = RippleLogger.new;
		logger.fileName = "RipplePlayer.log"
	}
	
	setParent{|parentArg|
		parent = parentArg;
	}
	
	getControlValue{|which|
		^parent.getControlValue(which);
	}
	
	setBuffer{|bufferArg|
		buffer = bufferArg;
	}
	
	setControls{|controlsArg| // a RippleControls
		controls = controlsArg; 
	}
	
	setControlValPostLaunch{|name, val|
		
		group.set( name, val );
	}
	
	calculateTime{|args|
		var time;
		time = ArrayUtils.getValueFromSynthArgs(args,\decay);
		time = time + ArrayUtils.getValueFromSynthArgs(args,\attack);
		time = time + ArrayUtils.getValueFromSynthArgs(args,\release);
		^time;
	}
	
	calculatePitchAndRatio{|pitchRatioArray|
	
		var pitch, ratio;
		
		pitch = pitchRatioArray[0];
		ratio = pitchRatioArray[1];
	
		if(
			ratio != nil,
			{
				pitch = (ratio.ratiomidi + SynthConstants.basePitch.cpsmidi).midicps;
				^[pitch,ratio]
			},
			{
				if(
					pitch != nil,
					{
						ratio = (pitch.cpsmidi - SynthConstants.basePitch.cpsmidi).midiratio;
						^[pitch,ratio]
					},
					{
					 ^this.calculatePitchAndRatio([nil,1]); 
					}
					
				)
			}	
		
		);
		
	}
	
	// stupid way to do this. shouldn't have to operate on both pitch and ratio since they're equivalent
	transpose{|pitchRatioArray|
		^[
			pitchRatioArray[0] * parent.getTranspose.midiratio,
			pitchRatioArray[1] * parent.getTranspose.midiratio
		]
	}
	
	launchSynth{|pitch, volume, pitchRatio, sustain = false|
		var args, s, subGroup, time, pitchRatioArray;
		
		if(volume == nil){
			volume = 1;
		};
		
		pitchRatioArray = this.calculatePitchAndRatio([pitch, pitchRatio]);
		pitchRatioArray = this.transpose(pitchRatioArray);
		
		pitch = pitchRatioArray[0];
		pitchRatio = pitchRatioArray[1];
		
//		if(this.getControlValue(\volume) * this.getControlValue(\volume2) == 0){
//			^nil; // exit method if final volume is zero
//		};
		
		try{
			launchSynthFn.value; // launchSynthFn hook
		}{"launchSynthFn failed".postln;};
		
		
		args = parent.getControlsAsArgsArray; // get values from controls
		
		this.makeSureLenthNotZero(args);
		
		if(volume != 1){
			ArrayUtils.setValueInSynthArgs(args, \volume, ArrayUtils.getValueFromSynthArgs(args, \volume) * volume);
		};
		
		
		if(
			OverloadPreventer.isSafeToAdd(this.getSynthType.getCPUIndex),
			{	
				if(sustain.not,{
					time = this.calculateTime(args);
				});
			
		
				
				if(buffer.notNil){
					args = args ++ [
						\bufnum, buffer.bufnum,
					];
				};
		
				args = args ++ [
					\pitch, pitch,
					\pitchRatio, pitchRatio,
					\busNum, RippleBusLibrary.checkoutBus(time),
					\mainOutBus, parent.getMainOutBus
				];
				
				if(poly != true){
					group.freeAll;
				};
				
				if(poly == true){
					this.getSynthType.balanceCheckbook(time); // add voices to OverloadPreventer
					// this should be done in all cases, but don't currently have a good way to remove when in mono mode
					// for now, just disregard when in mono mode and hope for the best
				};
				subGroup = Group.new(group); // the synth and it's \out are in this group
				Synth.tail(subGroup,this.getSynthType.getOutSynthName, args); // create the ouput Synth in the chain
				this.getSynthType.playSound(args, subGroup, buffer);
				{
					var paramName, logString;
					logString = "++++++++++++++++++++++++++++++++++++++++\n";
					logString = logString + parent.getName;
					logString = logString + "\n";
					args.do({|theArg, i|
						if( i % 2 == 0,
							{paramName = theArg},
							{logString = logString + paramName ++ ": " + theArg + "\n"}
						);
					});
					logger.log(logString);
				}.value;
				this.getSynthType.onPlaySound(args, subGroup, buffer);
				time = this.calculateTime(args); // doing this so we can increase time in case of fx added. Very messy.
				//subGroup.set(\gate,1);
				if(sustain,{
					sustainedNotes.add(SustainedNote.new(subGroup, args));
					parent.soundPlayed( -1 , sustain: true);
				},{
					//this.freeSubGroupWhenDone(subGroup, time);
					SystemClock.sched(
						ArrayUtils.getValueFromSynthArgs(args, \attack) + ArrayUtils.getValueFromSynthArgs(args, \decay),
					 	{subGroup.set(\gate,0);}
					);
					
					parent.soundPlayed( time );
					
				};);
			
			},
			{
				Ripple.tooManyVoices;
			}
		)
		
	
	}
	
	makeSureLenthNotZero{|args|
		ArrayUtils.setValueInSynthArgs(args, \decay, ArrayUtils.getValueFromSynthArgs(args, \decay).max(0.01))
	}
	
	releaseSound{
		parent.soundPlayed( 1, sustain: true); // set time to one just for now. this whole stuff needs to be refactored.
		//group.set(\gate,0);
		sustainedNotes.do({|note|
			note.release;
		});
		sustainedNotes = List.new;
	}
	
	freeSubGroupWhenDone{|subGroup, time|
		SystemClock.sched(time, {subGroup.free});
	}
	
	endLoop{
		var loopFrames;	
		
		if(
			this.getControlValue(\loopLength) > 0.95,
			{  loopFrames = buffer.numFrames },
			{  loopFrames = controls.at(\loopLength).getValue * 63338 }
		);
		
		^(this.getControlValue(\startPos) + loopFrames).min(buffer.numFrames);
	}
	
	
	
	getSynthType{
		^parent.getSynthType;
	}
	
	setPoly{|val|
		poly = val;
	}
	
	getPoly{
		^poly;
	}
	
	freeBuffer{
		buffer.free;
	}
	
	getNodeId{
		^group.nodeID;
	}

}

SustainedNote{
	var subGroup, args;
	
	*new{|subGroupArg, argsArg|
		^super.new.init(subGroupArg, argsArg);
	}
	
	init{|subGroupArg, argsArg, busNumArg|
		subGroup = subGroupArg; 
		args = argsArg;
	}

	release{
		subGroup.set(\gate, 0);
		SystemClock.sched(ArrayUtils.getValueFromSynthArgs(args, \decay) + 1, {
			//subGroup.free;
			RippleBusLibrary.returnBus(ArrayUtils.getValueFromSynthArgs(args, \busNum));
		});
	}
	
	
}










